Ethereum Multisignature Wallet by Gnosis
Gnosisによるイーサリアムのマルチシグウォレットを提供するスマートコントラクトについてまとめた。
このウォレットではイーサおよびトークンの入金、引き出しが可能となっている。
なお、Gnosisはグノーシスではなく「ノーシス」と読む。ただ実際にグノーシスと読む人もいるし、通じればどちらでも良い。
また、東京にある株式会社Gunosyと名前が似ているが、資本関係は無い。
前提条件
最終調査日: 2018年9月20日
検証環境
Node.js v8.12.0 (最新LTS版)
macOS High Sierra 10.13.6
Ethereum private network (Ganache v1.2.2)
Contracts概要
ウォレット
MultiSigWallet.sol
MultiSigWalletWithDailyLimit.sol
ファクトリー
MultiSigWalletFactory.sol (GUIやTruffleのmigrationでは使われてない)
MultiSigWalletWithDailyLimitFactory.sol
Factoryはウォレットのインスタンス化とインスタンス化されたウォレットのアドレス管理を行う。
ウォレットには通常のマルチシグウォレットとDailyLimit付きのマルチシグウォレットがある。
ウォレットは残高、日次の引き出し総額、オーナー (EOA)、トークン、トランザクション、トランザクションの承認 (confirmation) を管理する。
ウォレット作成時には1つ以上のオーナーアドレス、引き出し時に必要な承認数を指定する。
ウォレット作成後でもオーナーの追加、置換、削除は可能。承認数も変更可能。
DailyLimit付きのウォレットは日毎に引出額の上限を設定することができ、上限内であれば承認数が1つでも引き出しができる。上限を超えていれば、必要な承認数を集めなければ引き出しは実行されない。
ウォレットの作成
MultiSigWalletWithDailyLimitFactoryのcreateをcallして作成する。この時、
オーナーとなるアドレスのリスト
必要な承認数
承認数1で引き出せるETHの上限(24時間ごとにリセット)
を指定する。
成功するとウォレットのアドレスが返却される。
ウォレット作成は誰でも実行できる。msg.senderがオーナーのリストに含まれてなくても実行できる。
ウォレットの状態変更
トランザクション作成(submitTransaction)・承認(confirmTransaction)・実行(executeTransaction)の関数をcallしてウォレットの状態変更を行う。これら関数のcallはオーナーのみできる。
承認フローについてはイーサの入金と引き出しの項で詳しく解説する。
ウォレットオーナーの追加・変更・削除
ウォレット作成後にオーナーを追加・変更・削除できる。
オーナーの追加を行うトランザクションを作成するには、ウォレットのsubmitTransactionを利用して行う。この時submitTransacrtionの引数は
destination: ウォレットコントラクトのアドレス
value: 0
data: walletInstance.addOwner.getData(<追加したいオーナーアドレス>)
のように指定する。詳しくはテストコードを参照:
オーナーの変更・削除もオーナー追加と同様にsubmitTransactionを利用して行う。
この時dataには、オーナー変更の場合
walletInstance.replaceOwner.getData(<変更したいオーナーアドレス>, <変更後のオーナーアドレス>)
を指定する。オーナー削除の場合
walletInstance.removeOwner.getData(<削除したいオーナーアドレス>)
を指定する。
必要な承認数の変更
ウォレット作成後も必要な承認数の変更ができる。
必要な承認数の変更を行うには、オーナー追加と同様にsubmitTransactionを利用して行う。
この時dataには、
walletInstance.changeRequirement.getData(<新たに設定したい必要承認数>)
を指定する。
ちなみにオーナー数を超えた承認数に設定できないようmodifierで防がれている。
DailyLimitの変更
ウォレット作成後も日毎の引き出し上限額も変更できる。
日毎の引き出し上限額を変更するには、オーナー追加と同様にsubmitTransactionを利用して行う。
この時dataには、
walletInstance.changeDailyLimit.getData(<新たに設定したい上限額>)
を指定する。
イーサの入金と引き出し
ウォレットコントラクトのアドレスに対しイーサを送金するとウォレットにその額がデポジットされる。
2つのオーナーの承認が必要なウォレットの場合、デポジットしたイーサの引き出し手順は以下のようなシーケンス図で表せる。
https://gyazo.com/c5574e47ad2d38c22507f4a9191d949b
詳細な手順は以下のようになっている。
Owner 1はsubmitTransactionをcallする
この時コントラクトに、引き出し先アドレス (destination)、額 (value)、トランザクションに載せるデータペイロード (data) を指定して送信する
データは無くても問題ないが、ERC20トークンを引き出す際に利用する。また、引き出し先アドレスがEOAではなく別のコントラクトの場合などにも利用できる
コントラクトは届いた引き出し先アドレス、額、データをストレージに保存し、続けてconfirmTransactionを実行する
confirmTransactionでは実行前に、送信者がオーナーかどうか (ownerExists)、承認しようとしているトランザクションが存在するか (transactionExists)、送信者によってトランザクションが既に承認されていないか (notConfirmed) を確認する
modifierの処理が問題なければConfirmationイベントを発行し、ストレージに送信者がトランザクションを承認したことを保存する
コントラクトは、confirmTransactionが実行できたら、続けてexecuteTransactionを実行する
executeTransactionでは実行前に、送信者がオーナーかどうか (ownerExists)、実行しようとしているトランザクションが送信者によって承認されているか (confirmed)、トランザクションが既に実行されていないか (notExecuted) を確認する
modifierの処理が問題なければトランザクションが必要な承認の数を得ているか確認する
この時点ではまだ承認数が1なので、ここでコントラクトの処理は終了する
Owner 2はConfirmationイベントなどで承認待ちのトランザクションがあることを知ることができる
Owner 2はconfirmTransactionをcallする
コントラクトはconfirmTransactionを実行すると、続けてexecuteTransactionを実行する
この時点で承認数が2となり、引き出しに必要な条件が満たされる
コントラクトはストレージに保存したトランザクションの実行済みフラグを真に変更し、引き出し先アドレスにsubmitTransactionで指定した額とデータを送信する
送信が成功すればExecutionイベントを発行し、コントラクトの処理を終了する
送信が失敗したらExecutionFailureイベントを発行し、トランザクションの実効済みフラグを偽に変更し、コントラクトの処理を終了する
引き出しが完了する
引き出し処理の開始 (submitTransaction) と承認 (confirmTransaction) はオーナーしかcallできないが、コントラクトへの送金は誰でもしていいし、引き出し先アドレスの制約も無い。
トークンの入金と引き出し
イーサの場合と同様の手順で入金、引き出しができる。すなわちマルチシグでの運用が可能となっている。
submitTransactionを利用して引き出し処理を行う。この時submitTransacrtionの引数は
destination: トークンコントラクトのアドレス
value: 0
data: tokenInstance.transfer.getData(<トークン引き出し先のアドレス>, <トークン引き出し額>)
のように指定する。詳しくはテストコードを参照:
オフライン署名
Gnosis MultiSigWalletではオフライン環境で署名されたトランザクションの利用を想定した実装がなされている。すなわちコールドウォレットとしての利用が可能となっている。
もうちょっと調べたいところ
MultiSigWalletWithDailyLimitFactoryはMultiSigWalletを継承、executeTransactionをオーバーライドしているが、そうするとMultiSigWalletのexternal_callによるgas cost最適化の恩恵を受けられなくなるのでは?
DailyLimitの仕様の意図が分からない
仕様
1日の出金額がDailyLimit以下なら、設定された必要承認数を無視して1承認で引き出せる (該当コード) この時の出勤額を1日の出勤額に加算、ストレージに保存
DailyLimitを超えたら、必要な承認数を得ることでイーサの引き出しが実行できるようになる
24時間で1日の出金額がリセットされる
活用法予想
必要承認数を2に設定しておく
登録オーナーを以下のように設定する
1つはオンライン環境に秘密鍵を持つオーナー
もう1つはオフライン環境に秘密鍵を持つオーナー
DailyLimit設定額を100ETHに設定
以上の設定をすると、1日100ETHまではホットウォレットのように動作し、出金100ETHを超えたらマルチシグが必要なコールドウォレットのように動作し始める
取引所などで、1日あたり100ETHまではオペレーション効率化のためにオフライン署名を不要とすることができる
また、オンライン環境がマルウェア汚染するなどして秘密鍵が流出したとしても、このような設定をしておけば1日あたりのイーサ出金額を100に抑えつつ、他の操作(オーナー変更、トークンの出金など)はオフライン環境での署名が必要となり、大部分の資産を守ることができる
勉強会メモ
osuke.icon >
Daily limitの使い道わからん。
require(msg.sender == address(this));??
例えばaddOwnerはsubmitTransaction経由でウォレット上から呼び出されるらしい。MultiSigWallet.solのexternal_callのdestinationが自身のウォレットアドレスになる
yudetamago.icon >
external_callの中のassemblyは、普通にcallにしてしまうと実際のgasがestimatedGasの値とずれるので、あえてassemblyにしてgasの値を手動で調整しているみたい(?)
nrryuya.icon >
inline assemblyのgasはその時の残りのgasを返す